#ifndef DATA_H_INCLUDED
#define DATA_H_INCLUDED

#include <iostream>
#include <string>
#include <stdexcept>

using namespace std;

class Date
{
public:
    friend ostream &operator<<(ostream &, const Date &);

    Date() = default;
    Date(string &date_string);

    unsigned y() const { return year; }
    unsigned m() const { return month; }
    unsigned d() const { return day; }

private:
    unsigned year, month, day;
};

// month_names
const string MONTH_NAMES[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
const string MONTH_ABBR[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
const int MONTH_DAYS[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

inline int get_month(string &date_string, int &end_of_month)
{
    // args: the format str and the position point
    int month_idx, date_string_idx;
    for (month_idx = 0; month_idx < 12; month_idx++)
    {
        for (date_string_idx = 0; date_string_idx < MONTH_ABBR[month_idx].size(); date_string_idx++) // match the abbr.
        {
            if (date_string[date_string_idx] != MONTH_ABBR[month_idx][date_string_idx])
                break;
        }
        if (date_string_idx == MONTH_ABBR[month_idx].size()) // matched
        {
            break;
        }
    }
    if (month_idx == 12)
        throw invalid_argument("Invalid month");

    if (date_string[date_string_idx] == ' ') // month is abbr.
    {
        end_of_month = date_string_idx + 1;
        return month_idx + 1;
    }
    else
    {
        for (; date_string_idx < MONTH_NAMES[month_idx].size(); date_string_idx++)
        {
            if (date_string[date_string_idx] != MONTH_NAMES[month_idx][date_string_idx])
                break;
        }
        if (date_string_idx == MONTH_NAMES[month_idx].size() && date_string[date_string_idx] == ' ')
        {
            end_of_month = date_string_idx + 1;
            return month_idx + 1;
        }
        throw invalid_argument("Invalid month");
    }
}

inline int get_day(string &date_string, int month, int &p)
{
    size_t day_length; // length of the day
    int day = stoi(date_string.substr(p), &day_length);
    if (day < 1 || day > MONTH_DAYS[month])
        throw invalid_argument("Invalid day");
    p += day_length;
    return day;
}

inline int get_year(string &date_string, int &p)
{
    size_t year_length;
    int year = stoi(date_string.substr(p), &year_length);
    if (p + year_length < date_string.size())
    {
        throw invalid_argument("Invalid ending of date");
    }
    p += year_length;
    return year;
}

Date::Date(string &date_string)
{
    int p = 0;
    size_t x_length;
    string number_str = "0123456789";

    if ((p = date_string.find_first_of(number_str)) == string::npos)
        throw invalid_argument("Invalid date with no number");

    if (p > 0) // month is name format
    {
        month = get_month(date_string, p);
        day = get_day(date_string, month, p);
        if (date_string[p] == ',' || date_string[p] == ' ')
        {
            p++;
            year = get_year(date_string, p);
        }
        else
        {
            throw invalid_argument("Invalid date");
        }
    }
    else // month is number format
    {
        month = stoi(date_string, &x_length);
        p = x_length;
        if (month < 1 || month > 12)
            throw invalid_argument("Invalid month");
        if (date_string[p] != '/')
        {
            throw invalid_argument("Invalid seperator");
        }
        p++;
        day = get_day(date_string, month, p);
        if (date_string[p] != '/')
        {
            throw invalid_argument("Invalid seperator");
        }
        p++;
        year = get_year(date_string, p);
    }
}

ostream &operator<<(ostream &os, const Date &date)
{
    os << date.y() << "/" << date.m() << "/" << date.d();
    return os;
}
#endif // DATA_H_INCLUDED